import React from "react";
import { Route, Switch } from "react-router-dom";
import BaseView from "./BaseView";
import { Bi, Ei, Pi } from "@/module-00-codebase/pkg-01-container";
import { aw, Throwable } from "@/module-00-codebase/pkg-02-tool";

export default abstract class BasePage extends BaseView<any> {
  resultView: any = null;
  public render() {
    try {
      if (this.getMemory("__isInitializing") === true) {
        // skip
      } else if (this.isNeedInit()) {
        this.proxyPageInit(this.getArgs());
      } else if (this.getMemory("__pageError") instanceof Error) {
        throw this.getMemory("__pageError");
      } else {
        this.resultView = this.pageView();
      }
    } catch (ex) {
      const e = ex instanceof Error ? ex : new Error(JSON.stringify(ex));
      Bi.log.error(e.message);
      this.resultView = (
        <Ei.Layout params={[]}>
          <Ei.Text params={["Error: " + e.message]} />
        </Ei.Layout>
      );
    }
    return this.resultView;
  }
  public pageRoutePath(): string {
    return Throwable("Page route hasn't setted.");
  }
  public pageRead(key?: string): any {
    return Bi.utils.readValue(this.getStateData(), key || "");
  }
  private __statingsetting = false;
  public getStateData() {
    let stateData = page_persist_map.get(this.pageRoutePath());
    if (!stateData) {
      page_persist_map.set(
        this.pageRoutePath(),
        (stateData = { ...(Bi.cacheManager.read(this.cacheKey()) || {}) } as {
          [key: string]: any;
        })
      );
    }
    return stateData;
  }
  public pageWrite(key: string, value: any): void {
    Bi.utils.writeValue(this.getStateData(), key, value);
    if (
      this.getMemory("__pageInitialized") === true &&
      this.__statingsetting === false &&
      (this.__statingsetting = true)
    ) {
      (async () => {
        try {
          await aw(0);
          Bi.cacheManager.write(this.cacheKey(), this.getStateData());
          if (!this.isDestroyed)
            await aw(this.setState.bind(this), { ...this.getStateData() });
        } finally {
          this.__statingsetting = false;
        }
      })();
    }
  }
  public async pageInit(args: any) {}
  public pageRefresh() {
    this.pageWrite("any", Bi.utils.uuid());
  }
  public pageView() {
    return this.childView();
  }
  public childView() {
    const childView = this.getMemory("childView");
    if (!!childView) {
      return childView;
    } else {
      const currentPath = this.pageRoutePath();
      const routes: React.ReactElement[] = [];
      Object.keys(Pi).forEach((route) => {
        const Page = Pi[route];
        if (
          currentPath === "/" &&
          route !== currentPath &&
          route &&
          route.lastIndexOf("/") === 0
        ) {
          routes.push(
            <Route
              key={routes.length + 1}
              path={route}
              component={Page as any}
            />
          );
        } else if (
          route !== currentPath &&
          route.indexOf(currentPath) === 0 &&
          route.substring(currentPath.length).lastIndexOf("/") === 0
        ) {
          routes.push(
            <Route
              key={routes.length + 1}
              path={route}
              component={Page as any}
            />
          );
        }
      });
      const child = routes.length > 0 ? <Switch>{routes}</Switch> : <></>;
      this.setMemory("childView", child);
      return child;
    }
  }
  public async proxyPageInit(args: any) {
    if (this.isNeedInit()) {
      try {
        this.setMemory("__isInitializing", true);
        this.setArgs(args);
        this.setLast(args);
        await this.pageInit(args);
        this.setMemory("__pageError", undefined);
      } catch (ex) {
        const e = ex instanceof Error ? ex : new Error(JSON.stringify(ex));
        this.setMemory("__pageError", e);
        throw e;
      } finally {
        this.setMemory("__isInitializing", false);
        this.setMemory("__pageInitialized", true);
        this.pageRefresh();
      }
    }
  }
  private isNeedInit() {
    return (
      !this.getMemory("__pageInitialized") || //e
      !this.existArgs() ||
      !this.existLast() ||
      this.getArgs() !== this.getLast()
    );
  }
  private cacheKey() {
    return "page_data_" + this.pageRoutePath();
  }
  public getArgs() {
    return this.pageRead(argsValKey);
  }
  public setArgs(value: any) {
    this.pageWrite(argsValKey, value);
  }
  private existArgs() {
    return !!this.pageRead(argsCtnKey);
  }
  private getLast() {
    return this.getMemory(lastValKey);
  }
  public setLast(value: any) {
    this.setMemory(lastValKey, value);
  }
  private existLast() {
    return !!this.getMemory(lastCtnKey);
  }
  public clearArgs() {
    this.pageWrite(argsCtnKey, undefined);
  }
  private getMemory(key: string = "") {
    let memory = page_memory_map.get(this.cacheKey());
    if (!memory) {
      page_memory_map.set(this.cacheKey(), (memory = new Map()));
    }
    return Bi.utils.readValue(memory, key);
  }
  private setMemory(key: string, value: any) {
    const memory = this.getMemory();
    Bi.utils.writeValue(memory, key, value);
  }
  public isCurrentRoutePath() {
    return this.pageRoutePath() === Bi.navigate.getCurrentRoutePath();
  }
}
const argsCtnKey = "__args";
const argsValKey = `${argsCtnKey}.value`;
const lastCtnKey = "__last";
const lastValKey = `${lastCtnKey}.value`;
// the data which will be cleared once the application closed.
const page_memory_map = new Map<string, { [key: string]: any }>();
// the data which will be persisted.
const page_persist_map = new Map<string, { [key: string]: any }>();
